/* MIT License
 *
 * Copyright (c) 2012 Reuben Smith, Miami University
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.droidlike.vsig;


import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import java.io.IOException;


/**
 * TrafficModule.
 * 
 * @author smithre5
 */
abstract class TrafficModule implements Control
{    
    public TrafficModule(ExecutorService executor)
    {
        if (executor == null) {
            throw new IllegalStateException("executor cannot be null");
        }
        
        this.executor = executor;
        modulePath = null;
        moduleID = -1;
        moduleRate = MINIMUM_RATE;
        moduleTick = 0f;
        shouldUpdate = false;
    }
    
    
    /**
     * Clones this control for another spatial. Unused.
     * 
     * @param spatial the spatial to receive the clone
     * @return null
     */
    public Control cloneForSpatial(Spatial spatial)
    {
        return null;
    }
    
    
    /**
     * @return the rate at which this module is updating, in seconds
     */
    public final float getRate()
    {
        return moduleRate;
    }
    
    
    /**
     * @return the current module's path
     */
    public final String getScript()
    {
        return modulePath;
    }
    
    
    /**
     * Reads in the module's state from an importer.
     * 
     * @param im the importer used
     * @throws IOException 
     */
    public void read(JmeImporter im) throws IOException
    {
        // STUB
    }
    
    
    /**
     * Performs module-specific rendering.
     * 
     * @param rm RenderManager to be used
     * @param vp ViewPort to be used
     */
    public void render(RenderManager rm, ViewPort vp) 
    {
        // STUB
    }
    
    
    /**
     * Sets the rate, in seconds, at which the module is ready to update.
     * 
     * @param rate the new rate in seconds
     */
    public final void setRate(float rate)
    {
        moduleRate = Util.max(MINIMUM_RATE, rate);
    }
    
    
    /**
     * Assigns the module's external script.
     * 
     * @param path the path to the script
     */
    public final void setScript(String path)
    {
        if (Util.isBlank(path)) {
            return;
        }
        
        moduleID = executor.addModule(path);
        
        if (moduleID >= 0) {
            modulePath = path;
            
            // smithre5: Is this the best place to be calling this?
            executor.processModules();
        }
    }
    
    
    /**
     * Assigns the spatial used by this module.
     * 
     * @param spatial the spatial to assign
     */
    public void setSpatial(Spatial spatial)
    {
        if (this.spatial == null || spatial == null) {
            if (this.spatial != null) {
                this.spatial.removeControl(this);
            }
            
            this.spatial = spatial;
            
            if (this.spatial != null) {
                this.spatial.addControl(this);
            }
        }
    }
    
    
    /**
     * Performs module-specific logic. Classes inheriting from this should
     * override this method in the following format:
     * 
     * public void update(float tpf) 
     * {
     *     super.update(tpf);
     * 
     *     (!isModuleReady()) {
     *         return;
     *     }
     * 
     *     // custom logic
     * 
     *     super.updated();
     * }
     * 
     * @param tpf time in seconds spent per frame
     */
    public void update(float tpf)
    {
        if (!shouldUpdate) {
            moduleTick += tpf;
            if (moduleTick > moduleRate) {
                shouldUpdate = true;
                moduleTick = 0;
            }
        }
    }
    
    
    /**
     * Writes the module to a save state.
     * 
     * @param ex exporter used
     * @throws IOException 
     */
    public void write(JmeExporter ex) throws IOException
    {
        // STUB
    }
    
    
    /**
     * Used by inheriting classes to ask if the module is ready to update.
     * 
     * @return true if ready; false otherwise
     */
    protected final boolean isModuleReady()
    {
        return shouldUpdate;
    }
    
    
    /**
     * Used by inheriting classes to inform the module that the update has been
     * performed.
     */
    protected final void updated()
    {
        shouldUpdate = false;
    }
    
    
    // smithre5: System timer resolution varies, but 75ms Should(TM) cover most
    // systems' minimum capability.
    private static final float MINIMUM_RATE = 0.075f;
    
    protected ExecutorService executor;
    protected int moduleID;
    protected long[] moduleInput, moduleOutput;
    protected Spatial spatial;
    
    private String modulePath;
    private float moduleRate, moduleTick;
    private boolean shouldUpdate;
}